package com.py.framework.core.rest.support;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

/**
 * 统一异常处理
 * @author Leegern
 * @date   2018年3月15日
 */
public class ExtExceptionHandlerResolver extends ExceptionHandlerExceptionResolver {  
  
    private String defaultErrorView;  
  
    public String getDefaultErrorView() {  
        return defaultErrorView;  
    }  
  
    public void setDefaultErrorView(String defaultErrorView) {  
        this.defaultErrorView = defaultErrorView;  
    }  
    
    /*
     * (non-Javadoc)
     * @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.web.method.HandlerMethod, java.lang.Exception)
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {  
  
        if (handlerMethod == null) {
            return null;  
        }  
  
        Method method = handlerMethod.getMethod();  
  
        if (method == null) {  
            return null;  
        }  
        
        ModelAndView returnValue = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);  
        ResponseBody responseBodyAnnotation = AnnotationUtils.findAnnotation(method, ResponseBody.class);  
        RestController restControllerAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RestController.class);
        if (responseBodyAnnotation != null || restControllerAnnotation != null) {  
            try {  
                ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(method, ResponseStatus.class);  
                if (responseStatusAnn != null) {  
                    HttpStatus responseStatus = responseStatusAnn.value();  
                    String reason = responseStatusAnn.reason();  
                    if (!StringUtils.hasText(reason)) {  
                        response.setStatus(responseStatus.value());  
                    } else {  
                        try {  
                            response.sendError(responseStatus.value(), reason);  
                        } catch (IOException e) { }  
                    }  
                }  
                if (returnValue == null) {  
                	RestServiceResult restServiceResult = new RestServiceResult();
                	restServiceResult.setCode(RestServiceResult.CODE_UNKNOW);
                    String errorMsg = exception.getMessage();
                    //统一处理异常，不能把堆栈直接暴露给客户端
                    if (restServiceResult.getCode()== RestServiceResult.CODE_UNKNOW && org.apache.commons.lang.StringUtils.isNotEmpty(errorMsg)&&("结果集转换出错".equals(errorMsg))) {
                        errorMsg = "网络返回错误，请稍后重试。";
                    }
                    restServiceResult.setMsg(errorMsg);
                    restServiceResult.setData(new HashMap<String, Object>());
                    handleResponseError(restServiceResult, request, response);  
                    return new ModelAndView();  
                }
                return handleResponseBody(returnValue, request, response);  
            } catch (Exception e) {  
                return null;  
            }  
        }  
  
        if( null == returnValue) {  
            returnValue = new ModelAndView();  
            if (null == returnValue.getViewName()) {  
                returnValue.setViewName(defaultErrorView);  
            }  
        }  
        return returnValue;  
    }  
  
    @SuppressWarnings({ "unchecked", "rawtypes", "resource" })  
    private ModelAndView handleResponseBody(ModelAndView returnValue, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        Map value = returnValue.getModelMap();  
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);  
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();  
        if (acceptedMediaTypes.isEmpty()) {  
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);  
        }  
        MediaType.sortByQualityValue(acceptedMediaTypes);  
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);  
        Class<?> returnValueType = value.getClass();  
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();  
        if (messageConverters != null) {  
            for (MediaType acceptedMediaType : acceptedMediaTypes) {  
                for (HttpMessageConverter messageConverter : messageConverters) {  
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {  
                        messageConverter.write(value, acceptedMediaType, outputMessage);  
                        return new ModelAndView();  
                    }  
                }  
            }  
        }  
        if (logger.isWarnEnabled()) {  
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " + acceptedMediaTypes);  
        }  
        return null;  
    }  
    
    @SuppressWarnings({ "unchecked", "rawtypes", "resource" })  
    private ModelAndView handleResponseError(RestServiceResult returnValue, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);  
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();  
        if (acceptedMediaTypes.isEmpty()) {  
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);  
        }  
        MediaType.sortByQualityValue(acceptedMediaTypes);  
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);  
        Class<?> returnValueType = returnValue.getClass();  
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();  
        if (messageConverters != null) {  
            for (MediaType acceptedMediaType : acceptedMediaTypes) {  
                for (HttpMessageConverter messageConverter : messageConverters) {  
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {  
                        messageConverter.write(returnValue, acceptedMediaType, outputMessage);  
                        return new ModelAndView();  
                    }  
                }  
            }  
        }  
        if (logger.isWarnEnabled()) {  
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " + acceptedMediaTypes);  
        }  
        return null;  
    }  
}  